package com.gagakuai.datasource.pooled;

import com.gagakuai.reflect.ExceptionUtil;
import org.apache.commons.logging.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;


/*
 * @program: settement
 * @description: 池化的连接
 * @author: houhong
 * @create: 2022-11-08 22:27
 */
public class PoolConnection implements InvocationHandler {

    private static final Logger logger = LoggerFactory.getLogger(PoolConnection.class);

    private static final String CLOSE = "close";
    private static final Class<?>[] IFACE = new Class[]{Connection.class};
    /*
     * 真正的连接
     */
    private Connection realConnection;
    /*
     * 代理的连接
     */
    private Connection proxyConnection;

    private PoolDataSource poolDataSource;


    private long checkoutTimestamp;
    private long createdTimestamp;
    private long lastUsedTimestamp;
    private int connectionTypeCode;
    private boolean valied;

    private int hashCode = 0;


    public PoolConnection(Connection connection, PoolDataSource poolDataSource) {

        this.hashCode = connection.hashCode();
        this.realConnection = connection;
        this.createdTimestamp = System.currentTimeMillis();
        this.lastUsedTimestamp = System.currentTimeMillis();
        this.valied = true;
        this.realConnection = connection;
        this.poolDataSource = poolDataSource;
        this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(),
                IFACE, this);
    }


    public Connection getRealConnection() {
        return realConnection;
    }

    public Connection getProxyConnection() {
        return proxyConnection;
    }


    public void invalidate() {
        this.valied = false;
    }

    /*
    *  当前连接是否有效
    */
    public boolean isValid() {
        return valied && realConnection != null && poolDataSource.pingConnection(this);
    }

    public void setValied(boolean valied) {
        this.valied = valied;
    }

    @Override
    public int hashCode() {
        return hashCode;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof PoolConnection) {
            return realConnection.hashCode() == (((PoolConnection) obj).realConnection.hashCode());
        } else if (obj instanceof Connection) {
            return hashCode == obj.hashCode();
        } else {
            return false;
        }
    }

    public int getRealHashCode() {

        return realConnection == null ? 0 : realConnection.hashCode();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        String methodName = method.getName();

        //调用close, 忽略此方法，将该connection 放回连接池
        if (CLOSE.hashCode() == methodName.hashCode() && methodName.equals(CLOSE)) {
            poolDataSource.pushConnection(this);
            return null;
        } else {
            try {
                if (!Object.class.equals(method.getDeclaringClass())) {
                    //除了toString()方法，其他方法调用之前要检查connection是否还是合法的,不合法要抛出SQLException
                    checkConnection();
                }
                //其他的方法，则交给真正的connection去调用
                return method.invoke(realConnection, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        }
    }

    private void checkConnection() throws SQLException {
        if (!valied) {
            throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
        }
    }


    public long getCheckoutTime() {
        return System.currentTimeMillis() - checkoutTimestamp;
    }


    public void setRealConnection(Connection realConnection) {
        this.realConnection = realConnection;
    }

    public void setProxyConnection(Connection proxyConnection) {
        this.proxyConnection = proxyConnection;
    }

    public PoolDataSource getPoolDataSource() {
        return poolDataSource;
    }

    public void setPoolDataSource(PoolDataSource poolDataSource) {
        this.poolDataSource = poolDataSource;
    }

    public long getCheckoutTimestamp() {
        return checkoutTimestamp;
    }

    public void setCheckoutTimestamp(long checkoutTimestamp) {
        this.checkoutTimestamp = checkoutTimestamp;
    }

    public long getCreatedTimestamp() {
        return createdTimestamp;
    }

    public void setCreatedTimestamp(long createdTimestamp) {
        this.createdTimestamp = createdTimestamp;
    }

    public long getLastUsedTimestamp() {
        return lastUsedTimestamp;
    }

    public void setLastUsedTimestamp(long lastUsedTimestamp) {
        this.lastUsedTimestamp = lastUsedTimestamp;
    }

    public int getConnectionTypeCode() {
        return connectionTypeCode;
    }

    public void setConnectionTypeCode(int connectionTypeCode) {
        this.connectionTypeCode = connectionTypeCode;
    }

    public boolean isValied() {
        return valied;
    }

    public int getHashCode() {
        return hashCode;
    }

    public void setHashCode(int hashCode) {
        this.hashCode = hashCode;
    }
}